package com.ice.delay.task.core.config;

import com.ice.delay.task.util.CommonUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * @author ice
 * @Date 2022/5/14 0014 15:59
 */
@Slf4j
public class TimerWheel {

    private Thread thread = null;

    // 时间轮上的总刻度
    private int TICK = 60;

    // 时间轮上当前刻度
    private int wheelSlot = 0;

    // 时间轮
    private List<HashSet<TimerTask>> wheel = new ArrayList<>();

    private Boolean stop = false;

    // 上次执行时间
    private long lastTime = System.currentTimeMillis();

    private ThreadPoolTaskExecutor taskExecutor;

    public TimerWheel(ThreadPoolTaskExecutor taskExecutor) {
        this.taskExecutor = taskExecutor;
        init();
    }

    private void init() {
        for (int i = 0; i < TICK; i++) {
            wheel.add(new HashSet<>());
        }
        logger.info("时间轮已初始化完成");
        thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!stop) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    long now = System.currentTimeMillis();
                    quart(now);
                }
            }
        });
        thread.setName("Timer Wheel");
        thread.start();
    }

    public void stop() {
        this.stop = true;
    }

    /**
     * 添加一个定时器, 并返回定时器存根. 据此存根可在当前线程上取消定时器.
     *
     * @param sec sec秒后超时
     * @param cb  超时后回调, 返回true时表示定时器继续生效
     */
    public TimerTask addTimer(int sec, Function<Void, Boolean> cb) {
        return this.add(sec, cb);
    }

    /**
     * 添加一个定时器, 一次性使用.
     *
     * @param sec sec秒后超时
     * @param cb  超时后回调
     */
    public TimerTask addTimerOneTime(int sec, Consumer<Void> cb) {
        return this.addTimer(sec, tm -> {
            CommonUtil.exeConsumer(cb, null);
            return false;
        });
    }

    /**
     * 添加一个定时器.
     *
     * @param sec sec秒后超时
     * @param cb  超时后回调, 返回true时表示定时器继续生效
     */
    private TimerTask add(int sec, Function<Void, Boolean> cb) {
        int m = sec % this.TICK;
        int pos = this.wheelSlot + m;
        TimerTask tt = new TimerTask(sec, (sec / this.TICK), pos < this.TICK ? pos /* 放置在指针前面(还未到) */ : (pos - this.TICK) /* 放置在指针后面(已经过). */, cb);
        HashSet<TimerTask> solt = this.wheel.get(tt.slot);
        solt.add(tt);
        return tt;
    }

    /**
     * 跳动一个刻度.
     *
     * @param now
     */
    public void quart(long now) {
        // 不足1s,返回
        if (now - this.lastTime < 1000) {
            return;
        }
        this.lastTime = now;
        this.loop();
        this.wheelSlot++;
        this.wheelSlot = this.wheelSlot == TICK ? 0 : this.wheelSlot;
    }

    /**
     * 执行
     */
    private void loop() {
        HashSet<TimerTask> timerTasks = wheel.get(this.wheelSlot);
        List<TimerTask> arr = new ArrayList<>();
        timerTasks.forEach(v -> arr.add(v));

        List<TimerTask> next = new ArrayList<>();
        for (TimerTask task : arr) {
            if (task.loop == 0) {
                // 执行任务
                Future<Boolean> future = taskExecutor.submit(() -> CommonUtil.exeFunction(task.cb, null));
                try {
                    Boolean ret = future.get();
                    if (ret) {
                        // 继续等待下次超时
                        next.add(task);
                    }
                    timerTasks.remove(task);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            } else {
                task.loop--;
            }
        }
        for (TimerTask task : next) {
            int m = task.sec % this.TICK;
            int pos = this.wheelSlot + m;
            task.slot = pos < this.TICK ? pos /* 放置在指针前面(还未到) */ : (pos - this.TICK) /* 放置在指针后面(已经过). */;
            this.wheel.get(task.slot).add(task);
        }
    }

    @Data
    public static class TimerTask {
        //圈数
        private Integer loop;
        // 时间轮上的槽
        private Integer slot;
        // 超时时间
        private Integer sec;

        private Function<Void, Boolean> cb;

        public TimerTask() {

        }

        public TimerTask(Integer sec, Integer loop, Integer slot, Function<Void, Boolean> cb) {
            this.loop = loop;
            this.slot = slot;
            this.sec = sec;
            this.cb = cb;
        }
    }
}
